/*
 *  $Id: MutablePropPrimitive.java,v 1.1 2008/03/24 11:19:49 shingoki Exp $
 *
 *  Copyright (c) 2008 shingoki
 *
 *  This file is part of jpropeller, see http://jpropeller.sourceforge.net
 *
 *    jpropeller is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    jpropeller is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with jpropeller; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
package org.jpropelleralt.ref.impl;

import java.util.IdentityHashMap;
import java.util.Map;

import org.jpropelleralt.box.Box;
import org.jpropelleralt.box.BoxFeatures;
import org.jpropelleralt.box.impl.BoxFeaturesDefault;
import org.jpropelleralt.change.Change;
import org.jpropelleralt.change.ChangeDefault;
import org.jpropelleralt.multiverse.Multiverse;
import org.jpropelleralt.reaction.Reaction;
import org.jpropelleralt.ref.Ref;
import org.jpropelleralt.universe.Universe;

/**
 * A {@link Ref} that mirrors the value of another {@link Ref}
 * when that {@link Ref} has a value matching a given class, and is
 * null otherwise.
 * 
 * This essentially allows casting of a {@link Ref} from one
 * value type to another, where the casting returns null if not
 * possible.
 * 
 * @param <T>		The type of the {@link Ref} value
 */
public class ClassFilterRef<T> implements Ref<T> {

	private final Class<T> valueClass;
	private final BoxFeatures features;
	private final Ref<? super T> filteredRef;
	
	private ClassFilterRef(Universe universe, Class<T> valueClass, final Ref<? super T> filteredRef) {
		this.features = new BoxFeaturesDefault(this, universe);
		this.valueClass = valueClass;
		this.filteredRef = filteredRef;
		
		//Reaction listens to filtered Ref, and causes
		//a change to this Ref when it sees a change.
		ClassFilterReaction reaction = new ClassFilterReaction();
		filteredRef.features().addReaction(reaction);
		
		//Make sure reaction stays in scope
		features.registerReaction(reaction);
	}
	
	/**
	 * Create a {@link ClassFilterRef}
	 * @param <T>			The type of the {@link ClassFilterRef} value
	 * @param universe		The {@link Universe} this {@link ClassFilterRef} will belong to
	 * @param valueClass	The class of the {@link ClassFilterRef} value
	 * @param filteredRef	The {@link Ref} to be filtered
	 * @return				A new {@link ClassFilterRef}
	 */
	public static <T> ClassFilterRef<T> create(Universe universe, Class<T> valueClass, final Ref<? super T> filteredRef) {
		return new ClassFilterRef<T>(universe, valueClass, filteredRef);
	}

	/**
	 * Create a {@link ClassFilterRef}.
	 * This method is NOT typesafe, and is provided for the case
	 * where Java's type-erasure has struck again, leaving you knowing
	 * that a {@link Class} and a {@link Ref} MUST be of the same
	 * parametric type, but not what that type is. In this case, it
	 * is safe to use this method, and assume that the {@link ClassFilterRef}
	 * produced can be used as if it has the same parametric type.
	 * 
	 * @param universe		The {@link Universe} this {@link ClassFilterRef} will belong to
	 * @param valueClass	The class of the {@link ClassFilterRef} value - please ensure that
	 * 						this is actually a Class<T> for some known T
	 * @param filteredRef	The {@link Ref} to be filtered - please ensure that this is actually
	 * 						a Ref<T> for some known T
	 * @return				A new {@link ClassFilterRef}, which may be treated as if it
	 * 						is a ClassFilterRef<T>
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })	//See comments in javadoc
	public static ClassFilterRef createUnsafe(Universe universe, Class valueClass, final Ref filteredRef) {
		return new ClassFilterRef(universe, valueClass, filteredRef);
	}
	
	@Override
	public BoxFeatures features() {
		return features;
	}

	//We check that the value class (matching T) is assignable
	//from the value of the filtered ref, before setting it,
	//so cast is safe.
	@SuppressWarnings("unchecked")
	@Override
	public T get() {
		Multiverse.prepareRead(this);
		try {
			//Return the filtered value, if it is of correct class,
			//otherwise we must be null
			Object fVal = filteredRef.get();
			if (fVal == null) {
				return null;
			}
			
			if (valueClass.isAssignableFrom(fVal.getClass())) {
				return (T)fVal;
			} else {
				return null;
			}
		} finally {
			Multiverse.concludeRead(this);
		}
	}
	@Override
	public void reactionSet(T t, boolean propagate) {
		//We should never be told not to propagate - this indicates a reaction
		//is trying to apply to us, and we cannot meaningfully be the target
		//of a reaction, since this essentially makes the filtered Ref the
		//target of a reaction, but without the reaction knowing it is being redirected.
		//Luckily we will detect this and throw an exception
		if (!propagate) {
			throw new RuntimeException("ClassFilterRef was asked to set without propagating - this indicates it is being altered as a known target of a Reaction, this is NOT valid. A ClassFilterRef may not be a known target of a Reactions apply() method.");
		}
		
		Multiverse.prepareWrite(this);
		try {
			filteredRef.reactionSet(t, propagate);
		} finally {
			Multiverse.concludeWrite(this);
		}
	};

	@Override
	public Class<T> valueClass() {
		return valueClass;
	}

	@Override
	public void set(T value) {
		filteredRef.set(value);
	}
	
	private class ClassFilterReaction implements Reaction {
		
		IdentityHashMap<Box, Change> shallowChange;
		IdentityHashMap<Box, Change> deepChange;
		
		private ClassFilterReaction() {
			shallowChange = new IdentityHashMap<Box, Change>();
			shallowChange.put(ClassFilterRef.this, ChangeDefault.shallowInstance());
			deepChange = new IdentityHashMap<Box, Change>();
			deepChange.put(ClassFilterRef.this, ChangeDefault.deepInstance());
		}
		
		@Override
		public void apply(boolean propagate) {
			//Nothing to do
		}
		
		@Override
		public boolean completeChanges(Map<Box, Change> changes) {
			//Nothing to do, never need to be applied immediately
			return false;
		}
		@Override
		public Map<Box, Change> react(Map<Box, Change> changes) {
			//We only react to the filtered ref, so any change means
			//we have the same change.
			
			//We point to same instance if the 
			//filtered ref does - either we are
			//mirroring and will mirror the same
			//instance, or we are null and will
			//stay so. If filtered ref has a deep
			//change, we may not (if we stay null),
			//but it is acceptable to report shallow
			//when change is in fact deep.
			Change change = changes.get(filteredRef);
			if (change.shallow()) {
				return shallowChange;
			} else {
				return deepChange;
			}
		}
	}
}
